/**
 * 
 */
package net.phoenix.repository.query.lucene;

import java.io.IOException;

import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.jcr.observation.ObservationManager;

import net.phoenix.repository.observation.NodeEvent;
import net.phoenix.repository.property.BasicProperty;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.springframework.core.io.Resource;

/**
 * @author lixf
 * 
 */
public class IndexBuilder implements EventListener, Runnable, Constants {
	private static final Log log = LogFactory.getLog(IndexBuilder.class);

	private IndexWriterConfig indexWriterConfig;
	private IndexWriter indexWriter;
	private FSDirectory indexDirectory;
	private Thread thread;
	private boolean running;
	private BlockedQueue<NodeEvent> nodes;

	public IndexBuilder() {
		this.nodes = new BlockedQueue<NodeEvent>();
	}

	public void init() throws CorruptIndexException, LockObtainFailedException, IOException {
		if (this.thread != null)
			throw new IllegalStateException("Index builder started.");
		if (IndexWriter.isLocked(this.indexDirectory)) {
			IndexWriter.unlock(this.indexDirectory);
		}
		this.indexWriter = new IndexWriter(this.indexDirectory, this.indexWriterConfig);
		this.indexWriter.commit();
		thread = new Thread(this);
		thread.start();
		this.running = true;
		log.info("Index builder thread start.");
	}

	public void setObservationManager(ObservationManager manager) throws RepositoryException {
		manager.addEventListener(this, Event.NODE_ADDED | Event.NODE_MOVED | Event.NODE_REMOVED, null, false, null, null, true);
	}

	public void destroy() {
		this.running = false;
		log.info("Index builder thread stop.");
	}

	@Override
	public synchronized void onEvent(EventIterator events) {
		this.nodes.addAll(events);
	}

	protected void removeIndex(Node node) {
		try {
			indexWriter.deleteDocuments(new Term(INDEX_IDENTIFIER, node.getIdentifier()));
			indexWriter.commit();
		} catch (Exception ex) {
			log.error("Error in build index for " + node + ".", ex);
		}
	}

	protected void addIndex(Node node) {
		try {
			Document document = new Document();
			document.add(new Field(INDEX_IDENTIFIER, node.getIdentifier(), Field.Store.YES, Field.Index.NOT_ANALYZED));
			document.add(new Field(INDEX_PATH, node.getPath(), Field.Store.YES, Field.Index.NOT_ANALYZED));
			document.add(new Field(INDEX_PRIMARY_TYPE, node.getPrimaryNodeType().getName(), Field.Store.YES, Field.Index.NOT_ANALYZED));
			for (NodeType mixin : node.getMixinNodeTypes()) {
				document.add(new Field(INDEX_MIXIN_TYPE, mixin.getName(), Field.Store.YES, Field.Index.NOT_ANALYZED));
			}
			for (PropertyIterator iterator = node.getProperties(); iterator.hasNext();) {
				Property property = iterator.nextProperty();
				((BasicProperty) property).index(document);
			}
			indexWriter.addDocument(document);
			indexWriter.commit();
			log.info("Index build for node " + node + ".");
		} catch (Exception ex) {
			log.error("Error in build index for " + node + ".", ex);
		}
	}

	/**
	 * @param path
	 * @throws IOException
	 */
	public void setIndexPath(Resource path) throws IOException {
		this.indexDirectory = FSDirectory.open(path.getFile());
		log.info("Using " + this.indexDirectory.toString() + " as search index folder.");
	}

	/**
	 * @param indexWriterConfig
	 */
	public void setIndexWriterConfig(IndexWriterConfig indexWriterConfig) {
		this.indexWriterConfig = indexWriterConfig;
	}

	@Override
	public void run() {
		while (this.running) {
			Node node = null;
			try {
				NodeEvent event = this.nodes.getItem();
				node = event.getNode();
				switch (event.getType()) {
				case Event.NODE_ADDED:
					this.removeIndex(node);
					this.addIndex(node);
					break;
				case Event.NODE_MOVED:
					this.removeIndex(node);
					this.addIndex(node);
					break;
				case Event.NODE_REMOVED:
					this.removeIndex(node);
					break;
				}
			} catch (InterruptedException e) {
			}
		}

	}

}
